'''
Created on 2011-8-29

@author: Administrator
'''

import asyncore
import socket
import logging
import struct 
import pickle
from collections import deque
import datetime
import protocol


class Connecter(asyncore.dispatcher):
    buffer_size = 32768
    byte2short  = struct.Struct('H')
    last_ping_time = None
    
    def __init__(self, sock = None):
        if sock:
            asyncore.dispatcher.__init__(self, sock)
        else:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.send_list = deque()
        self.recv_data_list = []
        self.last_ping_time = datetime.datetime.now()
        
    def handle_read(self):
        data = self.recv(self.buffer_size)
        bodysize = 0
        while len(data):
            try:
                bodysize  = self.byte2short.unpack(buffer(data, 0, 2))[0]
            except struct.error as e:
                logging.warn('parse head error ' + str(e))
                return
            
            if bodysize > (len(data) - self.byte2short.size):
                logging.warn('package size not match')
                break
            self.recv_data_list.append(data[self.byte2short.size : (self.byte2short.size + bodysize)])
            data = data[(self.byte2short.size + bodysize) : ]
    
    def get_recv_data(self):
        data = self.recv_data_list
        self.recv_data_list = []
        return data
    
    def reset_socket(self):
        if self.socket:
            self.socket.close()
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
    
    def pull(self, data):
        if len(data) > self.buffer_size:
            raise ValueError('data length more than 32768')
        
        self.send_list.append(data)
        self.initiate_send()
        
    
    def handle_write(self):
        self.initiate_send()
        
    
    def initiate_send(self):
        while self.send_list and self.connected:
            first = self.send_list[0]
            # handle empty string/buffer or None entry
            if not first:
                del self.send_list[0]
                if first is None:
                    self.handle_close()
                    return

            # handle classic producer behavior
            data = self.byte2short.pack(len(first)) + first
            
            try:
                num_sent = self.send(data)
            except socket.error:
                self.handle_error()
                del self.send_list[0]
                return

            if num_sent:
                if num_sent < len(data) :
                    logging.warn('not send all data size'  + str(len(data)))
                    self.send_list[0] = first[num_sent:] 
                    del self.send_list[0]
                    return
            # we tried to send some actual data
            del self.send_list[0]
            return
        
    def log_info(self, message, type='info'):
        logging.log(logging._levelNames[type.upper()], message)
        

class ConnecterManager():
    def __init__(self):
        self.connecters = {}
        self.handlerlist = {}
        self.connect_handle = []
        self.disconnect_handler = []
        self.ping_Interval = datetime.timedelta(seconds = 10)
        self.RegisterProtocol(protocol.c2s_ping_request, self.OnPingRequest)
        self.current_time = None
        
    def AddConnect(self, socket):
        client = Connecter(socket)
        mark = str(socket.getpeername())
        self.connecters[mark] = client
        for v in self.connect_handle:
            v(mark)
        
    
    def OnData(self, mark, data):
        protocol = None
        fun = None
        try:
            protocol  = pickle.loads(data)
            fun = self.handlerlist[protocol.id]
        except pickle.PickleError as e:
            logging.exception(e)
            return
        except Exception as e:
            logging.exception(e)
            return
        
        fun(mark, protocol)
        
        
    def DoSend(self, mark, data):
        if mark not in self.connecters:
            logging.warn('not exits client %s' % mark)
            return
        
        try:
            data = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
        except pickle.PickleError as e:
            logging.exception(e)
            return
        
        self.connecters[mark].pull(data)
        
    def Loop(self):
        disconnectlist = []
        self.current_time = datetime.datetime.now()
        ping_request = protocol.S2CPingRequest()

        for k, v in self.connecters.items():
            for data in v.get_recv_data():
                self.OnData(k, data)
            
            if  self.current_time - v.last_ping_time > self.ping_Interval:
                self.CloseConnecter(k)
            
            if v.connected == False:
                disconnectlist.append(k)
                for funv in self.disconnect_handler:
                    funv(k)
        
        for v in disconnectlist:
            logging.info("%s disconnect", v)
            self.connecters.pop(v)
            
    def AddConnectHandler(self, fun):
        if fun in self.connect_handle:
            raise ValueError('aleady add connect handler')
        self.connect_handle.append(fun)
    
    def AddDisconnectHandler(self, fun):
        if fun in self.disconnect_handler:
            raise ValueError('aleady add disconnect handler')
        self.disconnect_handler.append(fun)
        
    def RegisterProtocol(self, id, fun):
        if id in self.handlerlist:
            raise ValueError(''.fromat('id {0} has in list', id))

        
        if type(fun) != type(self.OnData):
            raise ValueError('fun need function')
        
        self.handlerlist[id] = fun
        
    def UnregisterProtocol(self, id):
        self.handlerlist.pop(id)
    
    
    def GetConnecter(self, mark):
        return self.connecters.get(mark)
    
    def CloseConnecter(self, id):
        if id not in self.connecters:
            raise ValueError('no exist id')
        
        self.connecters[id].close()
        
    
    def GetIP(self, id):
        if id not in self.connecters:
            raise ValueError('no exist id')
        
        ip, port  = self.connecters[id].getpeername()
        return ip
    
    
    def OnPingRequest(self, connectid, request):
        c = self.GetConnecter(connectid)
        c.last_ping_time = self.current_time
